外部ネットワークアクセスで AWS サービスに対する IAM 認証を試してみた  #SnowflakeDB

外部ネットワークアクセスで AWS サービスに対する IAM 認証を試してみた #SnowflakeDB

Clock Icon2024.11.08

はじめに

2024年7月末のアップデートで外部ネットワークアクセスを使用する Snowpark UDF やストアドプロシージャから AWS サービスに対する IAM 認証のサポートがパブリックプレビューとなりました。こちらを試してみましたので、本記事で内容をまとめてみます。

https://docs.snowflake.com/en/release-notes/2024/8_37#authentication-with-aws-iam-from-procedures-and-functions-preview

アップデートの概要

Snowflake では外部ネットワークアクセスとして UDF やストアドプロシージャから外部の API を呼び出す処理を作成できます。この機能はすでに一般提供となっており、例えば以下の記事では Snowflake から dbt Cloud の API を呼び出し、タスクを使用しジョブをトリガーしています。

https://dev.classmethod.jp/articles/snowflake-dbt-job-triggered-by-task/

外部ネットワークから AWS リソースにアクセスする場合、これまでは IAM アクセスキーを使用する必要がありましたが、今回のアップデートにより、Snowflake から各種 AWS サービスへ直接 IAMロールで認証できるようになりました。これにより、一時的な認証情報を使用し、アクセスキーを使わずに安全にリソースへアクセスできるため、セキュリティリスクが低減されます。

前提条件

ここでは以下の環境で検証を行いました。

  • Python UDF を使用
  • UDF から外部ネットワークアクセスで Amazon API Gateway エンドポイントを使用した Lambda 関数を呼び出す
    • この際、IAM認証を使用する

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/services-apigateway.html

事前準備

Lambda 関数の作成

下図の設定で関数を作成しました。

image

実際に行われる処理は以下のように event オブジェクトから name の値を取得し、その値に応じて異なる挨拶メッセージを返してくれるようにしました。

import json

def lambda_handler(event, context):
    # eventからnameの値を取得
    name = event.get('name')

    # nameがある場合は "Hello, name" の形式で、ない場合は "Hello!" を返す
    greeting = f"Hello, {name}" if name else "Hello!"

    return {
        'statusCode': 200,
        'body': json.dumps(greeting)
    }

処理ができたら保存してテストを実行しておきました。

API Gateway の作成

続けて API Gateway を作成します。「API を作成」から「REST API」 を作成します。

image 1

リソースを以下のパスで作成しました。

image 2

メソッドのタイプを「POST」として統合タイプに「Lambda 関数」を指定します。

image 3

IAM 認証を使用したいので、REST API の IAM 認証を有効にします。「メソッドリクエスト」を編集します。

image 4

[認可] に「AWS IAM」を指定し保存します。

image 5

作成した API をここではdevステージを作成しデプロイしました。

https://repost.aws/ja/knowledge-center/iam-authentication-api-gateway

IAM ロールの作成

API Gateway へのアクセスに使用する IAM ロールを以下の設定で作成します。

  • 信頼されたエンティティのタイプ:AWS アカウント
  • オプション
    • 外部 ID を要求する

アカウントIDには自身のアカウントを指定し、外部 ID にも適当な値を指定しておきます。これらの値は後述する手順で更新します。今回はリソースポリシーでアクセス制御を行うため、IAM ポリシーは特に関連付けませんでした。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "sts:AssumeRole",
            "Principal": {
                "AWS": "<自身のAWSアカウントID>"
            },
            "Condition": {
                "StringEquals": {
                    "sts:ExternalId": "<任意の値>"
                }
            }
        }
    ]
}

API Gateway のリソースポリシーを定義

次に先ほど作成した API Gateway のリソースポリシーを編集します。IAM ロールの Arn を使用し、以下の内容でリソースポリシーを作成しました。Resourceは API Gateway のメソッド ARN を指定します。

{
    "Version": "2012-10-17",
    "Statement":
    [
        {
        "Effect": "Allow",
        "Principal":
            {
            "AWS": "arn:aws:sts::xxxxxxxxxxxx:assumed-role/<IAMロール名>/snowflake"
            },
        "Action": "execute-api:Invoke",
        "Resource": "arn:aws:execute-api:ap-northeast-1:xxxxxxxxxxxx:xxxxxx/*/POST/hello"
        }
    ]
}

上記は以下を参考としました。

https://docs.snowflake.com/ja/sql-reference/external-functions-creating-aws-ui-proxy-service

ポリシーを保存後、再度デプロイしておきます。

Snowflake側の作業

ここから Snowflake 側でも作業を行います。IAM認証を使用する外部ネットワークアクセスでは、以下の作業を行います。

  • ネットワークルールの作成
  • AWS IAM 認証用のセキュリティ統合の作成
  • シークレットの作成
  • 外部アクセス統合の作成

ネットワークルールの作成

はじめにMODE = EGRESSとするネットワークルールを作成します。これにより外部ネットワークへのアクセスの許可・制限を行います。ネットワークルールはスキーマレベルのオブジェクトなので、何らかのスキーマを指定し作成します。

VALUE_LISTには、対象の API のホストを指定しました。

CREATE OR REPLACE NETWORK RULE aws_api_gateway_network_rule
  MODE = EGRESS
  TYPE = HOST_PORT
  VALUE_LIST = ('{api_id}.execute-api.ap-northeast-1.amazonaws.com');

https://docs.snowflake.com/en/sql-reference/sql/create-network-rule

セキュリティ統合の作成

次に、IAM を使用する外部認証用のセキュリティ統合を作成します。これにより、Snowflake アカウント側でセキュリティ統合が参照する IAM ユーザーが作成されます。AWS_ROLE_ARNには上記の手順で作成した IAM ロールの Arn を指定します。デフォルトでは ACCOUNTADMIN のみ実行可能です。

CREATE OR REPLACE SECURITY INTEGRATION aws_api_gateway_security_integration
  TYPE = API_AUTHENTICATION
  AUTH_TYPE = AWS_IAM
  ENABLED = TRUE
  AWS_ROLE_ARN = 'arn:aws:iam::xxxxxxxxxxxx:role/snowflake-api-gateway-role';

https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-aws-iam

統合オブジェクトを作成後、以下を実行します。

DESC SECURITY INTEGRATION aws_api_gateway_security_integration;

出力の内、以下の内容を後ほど使用するので控えておきます。

  • API_AWS_IAM_USER_ARN
    • 統合オブジェクトが参照する Snowflake アカウント側の IAM ユーザー
  • API_AWS_EXTERNAL_ID

AWS側:IAM ロールの信頼関係を更新

Snowflake と AWS 側で作成済みの IAM ロール間の信頼関係の設定を行います。先の手順で取得した API_AWS_IAM_USER_ARN と API_AWS_EXTERNAL_ID の値でロールの信頼関係を以下の通り更新し保存します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "sts:AssumeRole",
            "Principal": {
                "AWS": "<API_AWS_IAM_USER_ARN>"
            },
            "Condition": {
                "StringEquals": {
                    "sts:ExternalId": "<API_AWS_EXTERNAL_ID>"
                }
            }
        }
    ]
}

シークレットの作成

外部サービスに必要な資格情報を保持するためにTYPE = CLOUD_PROVIDER_TOKEN とするシークレットを作成します。API_AUTHENTICATIONには上記の手順で作成したセキュリティ統合オブジェクトを指定します。シークレットもスキーマレベルのオブジェクトのため、何らかのスキーマを指定し作成します。

CREATE OR REPLACE SECRET aws_api_gateway_access_token
  TYPE = CLOUD_PROVIDER_TOKEN
  API_AUTHENTICATION = aws_api_gateway_security_integration;

https://docs.snowflake.com/en/sql-reference/sql/create-secret#aws-iam-required-parameters

外部アクセス統合の作成

さいごに外部アクセス統合を作成し、これまでに作成した各種オブジェクトを紐づけます。注意点として、現時点ではトライアルアカウントはサポートされていません。デフォルトでは ACCOUNTADMIN のみ実行可能です。

CREATE OR REPLACE EXTERNAL ACCESS INTEGRATION aws_api_gateway_external_access_integration
  ALLOWED_NETWORK_RULES = (aws_api_gateway_network_rule)
  ALLOWED_AUTHENTICATION_SECRETS = (aws_api_gateway_access_token)
  ENABLED = TRUE
  COMMENT = 'Test API Gateway connectivity';

https://docs.snowflake.com/en/sql-reference/sql/create-external-access-integration

PythonUDF の作成

IAM 認証で作成した API を呼び出す Python UDF を定義します。ここでは以下の内容で UDF を作成しました。

CREATE OR REPLACE FUNCTION aws_api_gateway_post_function(name STRING)
  RETURNS VARCHAR
  LANGUAGE PYTHON
  EXTERNAL_ACCESS_INTEGRATIONS = (aws_api_gateway_external_access_integration)
  RUNTIME_VERSION = '3.11'
  SECRETS = ('cred' = <db>.<schema>.aws_api_gateway_access_token)
  PACKAGES = ('requests', 'aws-requests-auth')
  HANDLER = 'main_handler'
AS
$$
from aws_requests_auth.aws_auth import AWSRequestsAuth
import requests
import _snowflake
import json

def main_handler(name):
    # シークレットから一時的な認証情報を取得
    cloud_provider_object = _snowflake.get_cloud_provider_token('cred')

    # AWSRequestsAuthに直接資格情報を渡す
    auth = AWSRequestsAuth(
        aws_access_key=cloud_provider_object.access_key_id,
        aws_secret_access_key=cloud_provider_object.secret_access_key,
        aws_token=cloud_provider_object.token,
        aws_host='{api_id}.execute-api.ap-northeast-1.amazonaws.com',
        aws_region='ap-northeast-1',
        aws_service='execute-api'
    )

    # nameパラメータでペイロードを定義
    payload = {'name': name}

    # API GatewayのエンドポイントにPOSTリクエストを送信
    url = 'https://{api_id}.execute-api.ap-northeast-1.amazonaws.com/dev/hello'
    response = requests.post(url, json=payload, auth=auth)

    # JSONレスポンスをパースしてbodyを抽出
    response_data = json.loads(response.text)
    body_content = json.loads(response_data["body"])

    return body_content
$$;

ポイントは以下です。

  • Snowflake 内に保存されているシークレットデータ(認証情報やAPIトークンなど)の取得には_snowflakeモジュールを使用
    • get_cloud_provider_token(cloud_provider_secret_name)でセッションを作成するための情報を持つオブジェクトが返される
    • 属性として、access_key_idsecret_access_keytokenが含まれる

https://docs.snowflake.com/en/developer-guide/external-network-access/secret-api-reference#python-api-for-secret-access

  • API Gateway に認証付きリクエストを送信するために、AWSRequestsAuth を使用
    • こちらは以下を参考とさせていただきました

https://dev.classmethod.jp/articles/python-v4-signature-without-boto3/#aws-requests-auth

UDF を実行

定義した UDF を実行してみます。
実行結果は以下の通りとなり、API Gateway 経由で Lambda 関数を実行し結果を取得することができました。

>SELECT aws_api_gateway_post_function('Alice');
+----------------------------------------+
| AWS_API_GATEWAY_POST_FUNCTION('ALICE') |
|----------------------------------------|
| Hello, Alice                           |
+----------------------------------------+
1 Row(s) produced. Time Elapsed: 3.982s

>SELECT aws_api_gateway_post_function('John');
+---------------------------------------+
| AWS_API_GATEWAY_POST_FUNCTION('JOHN') |
|---------------------------------------|
| Hello, John                           |
+---------------------------------------+
1 Row(s) produced. Time Elapsed: 2.101s

さいごに

Snowpark UDF から AWS サービスに対する IAM 認証を使用する外部ネットワークアクセスを定義してみました。一時的な認証情報を使用するので、AWS のプラクティスにも沿って Snowflake からアクセスできます。
本記事では API Gateway を使用してみましたが、公式ドキュメントでは S3 に対するアクセスを IAM 認証で行う手順がサンプルとして公開されています。

https://docs.snowflake.com/en/developer-guide/external-network-access/external-network-access-examples#accessing-amazon-s3-with-aws-iam

他にも以下では Amazon Bedrock に対して IAM 認証を行う例が記載されています。

https://medium.com/snowflake/iam-role-auth-with-snowpark-external-access-for-generative-ai-with-snowflake-and-aws-9273bd57f905

https://quickstarts.snowflake.com/guide/getting_started_with_bedrock_streamlit_and_snowflake/index.html#4

各サービスで利用できるの積極的に使っていきたい機能と思います。
こちらの内容が何かの参考になれば幸いです。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.